#include "SceneIO.h"
#include <iostream>
#include <set>
using namespace std;
#include <QtXml>

#include "Loader.h"
#include "Scene.h"
#include "Object.h"
#include "Light.h"
#include "ObjMesh.h"
#include "Material.h"


namespace
{

QString furnitureTypeToString(FurnitureType type)
{
    switch(type)
    {
        case CHAIR:
            return "CHAIR";
        case DESK:
            return "DESK";
        case GENERAL:
            return "GENERAL";
        case BUILDING:
            return "BUILDING";
    }
}

FurnitureType stringToFurnitureType(const QString& type)
{
    if(type == "CHAIR")
        return CHAIR;

    if(type == "DESK")
        return DESK;

    if(type == "GENERAL")
        return GENERAL;

    if(type == "BUILDING")
        return BUILDING;
}

}


SceneIO::SceneIO(const std::string& path)
{
    file = new QFile(QString::fromStdString(path));
    doc = new QDomDocument();
}

SceneIO::~SceneIO()
{
    delete file;
    delete doc;
}

void SceneIO::saveScene(Scene* scene)
{
    //TODO: Verificar se o arquivo já existe?
    if(!file->open(QIODevice::WriteOnly | QIODevice::Text))
    {
        cout << "Erro ao abrir o arquivo." << endl;
        return;
    }

    QDomElement root = createRootElement(scene);
    doc->appendChild(root);

    QTextStream stream(file);
    stream << doc->toByteArray(4);
    stream.flush();
    file->close();
}

QDomElement SceneIO::createRootElement(Scene* scene)
{
    QDomElement root = doc->createElement("scene");
    root.setAttribute("libraryPath", Loader::getLibraryPath());

    root.appendChild(doc->createComment("List of objects."));

    list<Object*> objects =  scene->getObjects();
    for(list<Object*>::iterator it = objects.begin(); it != objects.end(); ++it)
    {
        QDomElement primElement = createObjectElement(*it);
        root.appendChild(primElement);
    }


    root.appendChild(doc->createComment("List of Lights."));
    list<Light*> lights =  scene->getLights();
    for(list<Light*>::iterator it = lights.begin(); it != lights.end(); ++it)
    {
        QDomElement primElement = createLightElement(*it);
        root.appendChild(primElement);
    }

    return root;
}

QDomElement SceneIO::createObjectElement(Object* object)
{
    QDomElement primElement = doc->createElement("object");
    primElement.setAttribute("type", furnitureTypeToString(object->getObjMesh()->getType()));
    primElement.setAttribute("name", QString::fromStdString(object->getObjMesh()->getName()));
    primElement.setAttribute("tag", QString::fromStdString(object->getName()));

    QDomElement transformElement = createTransformElement(object->getTransform());
    primElement.appendChild(transformElement);

    QDomElement materialElement = createMaterialElement(object->getMaterial());
    primElement.appendChild(materialElement);

    return primElement;
}

QDomElement SceneIO::createLightElement(Light *light)
{
    QDomElement lightElement = doc->createElement("light");
    lightElement.setAttribute("tag", QString::fromStdString(light->getName()));

    QDomElement positionElement = doc->createElement("position");
    Point3f pos = light->getPosition();
    positionElement.setAttribute("value", QString("%1 %2 %3").arg(pos.x).arg(pos.y).arg(pos.z));
    lightElement.appendChild(positionElement);

    QDomElement diffuseElement = doc->createElement("diffuse");
    Vector3f diffuse = light->getDiffuse();
    diffuseElement.setAttribute("value", QString("%1 %2 %3").arg(diffuse.x).arg(diffuse.y).arg(diffuse.z));
    lightElement.appendChild(diffuseElement);

    QDomElement specularElement = doc->createElement("specular");
    Vector3f specular = light->getDiffuse();
    specularElement.setAttribute("value", QString("%1 %2 %3").arg(specular.x).arg(specular.y).arg(specular.z));
    lightElement.appendChild(specularElement);

    return lightElement;
}

QDomElement SceneIO::createTransformElement(const Transform& transform)
{
    QDomElement transformElement = doc->createElement("transform");

    QDomElement scaleElement = doc->createElement("scale");
    Vector3f scale = transform.getScale();
    scaleElement.setAttribute("value", QString("%1 %2 %3").arg(scale.x).arg(scale.y).arg(scale.z));
    transformElement.appendChild(scaleElement);

    QDomElement translateElement = doc->createElement("translate");
    Vector3f translate = transform.getTranslation();
    translateElement.setAttribute("value", QString("%1 %2 %3").arg(translate.x).arg(translate.y).arg(translate.z));
    transformElement.appendChild(translateElement);

    Vector3f rot = transform.getRotation();
    QDomElement rotElement = doc->createElement("rotate");
    rotElement.setAttribute("value", QString("%1 %2 %3").arg(rot.x).arg(rot.y).arg(rot.z));
    transformElement.appendChild(rotElement);

    return transformElement;
}

QDomElement SceneIO::createMaterialElement(Material* m)
{
    QDomElement materialElement = doc->createElement("material");

    QDomElement diffuseElement = doc->createElement("diffuse");
    Vector3f diffuse = m->getDiffuse();
    diffuseElement.setAttribute("value", QString("%1 %2 %3").arg(diffuse[0]).arg(diffuse[1]).arg(diffuse[2]));
    materialElement.appendChild(diffuseElement);

    QDomElement specularElement = doc->createElement("specular");
    Vector3f specular = m->getDiffuse();
    specularElement.setAttribute("value", QString("%1 %2 %3").arg(specular[0]).arg(specular[1]).arg(specular[2]));
    materialElement.appendChild(specularElement);

    QDomElement shininessElement = doc->createElement("shininess");
    shininessElement.setAttribute("value", QString::number(m->getShininess()));
    materialElement.appendChild(shininessElement);

    return materialElement;
}




Scene* SceneIO::loadScene()
{
    if(!file->open(QIODevice::ReadWrite | QIODevice::Text))
    {
        cout << "Erro ao abrir o arquivo." << endl;
        return NULL;
    }

    QString errorStr;
    int errorLine;
    int errorColumn;
    if (!doc->setContent(file, false, &errorStr, &errorLine, &errorColumn))
    {
        std::cerr << "Error: Parse error at line " << errorLine << ", "
                  << "column " << errorColumn << ": "
                  << qPrintable(errorStr) << std::endl;
        return NULL;
    }

    QDomElement root = doc->documentElement();
    if (root.tagName() != "scene")
    {
        std::cerr << "Error: Not a scene file" << std::endl;
        return NULL;
    }

    Scene* scene = new Scene();
    parseRootElement(root);

    for(unsigned int i=0; i<m_objects.size(); ++i)
        scene->addObject(m_objects[i]);

    for(unsigned int i=0; i<m_lights.size(); ++i)
        scene->addLight(m_lights[i]);

    return scene;
}

void SceneIO::parseRootElement(const QDomElement &root)
{
    Loader::setLibrary(root.attribute("libraryPath"));

    QDomElement element = root.firstChildElement("object");
    while(!element.isNull())
    {
        m_objects.push_back(parseObjectElement(element));
        element = element.nextSiblingElement("object");
    }

    element = root.firstChildElement("light");
    while(!element.isNull())
    {
        m_lights.push_back(parseLightElement(element));
        element = element.nextSiblingElement("light");
    }
}

Object* SceneIO::parseObjectElement(const QDomElement &element)
{
    Material* material = NULL;
    QDomElement child = element.firstChildElement("material");
    if(!child.isNull())
        material = parseMaterial(child);

    Transform transform;
    child = element.firstChildElement("transform");
    if(!child.isNull())
        transform = parseTransform(child);

    Texture *texture = NULL;
    ObjMesh* mesh = Loader::loadMesh(stringToFurnitureType(element.attribute("type")), element.attribute("name"), &texture);
    if(texture)
        material->setTexture(texture);

    Object* obj = new Object(mesh, material, transform);
    obj->setName(element.attribute("tag").toStdString());

    return obj;
}

Light *SceneIO::parseLightElement(const QDomElement &element)
{
    QDomNode node = element.firstChild();

    Point3f pos;
    Vector3f diffuse;
    Vector3f specular;

    while(!node.isNull())
    {
        if(node.isElement())
        {
            QDomElement child = node.toElement();

            if(child.tagName() == "position")
            {
                float* array = parseFloatArray3(child.attribute("value"));
                pos = Point3f(array);
                delete[] array;
            }
            else if(child.tagName() == "diffuse")
            {
                float* array = parseFloatArray3(child.attribute("value"));
                diffuse = Vector3f(array);
                delete array;
            }
            else if(child.tagName() == "specular")
            {
                float* array = parseFloatArray3(child.attribute("value"));
                specular = Vector3f(array);
                delete array;
            }
        }

        node = node.nextSibling();
    }

    Light *light = new Light(pos, diffuse, specular);
    light->setName(element.attribute("tag").toStdString());
    return light;
}

Material* SceneIO::parseMaterial(const QDomElement &element)
{
    Material *material = new Material();
    QString value;

    QDomElement child = element.firstChildElement("diffuse");
    if(!child.isNull())
    {
        value = child.attribute("value");
        float *array = parseFloatArray3(value);
        material->setDiffuse(array[0], array[1], array[2]);
        delete[] array;
    }


    child = element.firstChildElement("specular");
    if(!child.isNull())
    {
        value = child.attribute("value");
        float *array = parseFloatArray3(value);
        material->setSpecular(array[0], array[1], array[2]);
        delete[] array;
    }

    child = element.firstChildElement("shininess");
    if(!child.isNull())
        material->setShininess(child.attribute("value").toFloat());

    return material;
}

Transform SceneIO::parseTransform(const QDomElement &element)
{
    QDomNode node = element.firstChild();
    Transform transform;
    while(!node.isNull())
    {
        if(node.isElement())
        {
            QDomElement child = node.toElement();

            if(child.tagName() == "translate")
            {
                float* array = parseFloatArray3(child.attribute("value"));
                transform.setTranslation(Vector3f(array));
                delete[] array;
            }
            else if(child.tagName() == "scale")
            {
                float* array = parseFloatArray3(child.attribute("value"));
                transform.setScale(array);
                delete array;
            }
            else if(child.tagName() == "rotate")
            {
                float* array = parseFloatArray3(child.attribute("value"));
                transform.setRotation(array);
                delete array;
            }
        }

        node = node.nextSibling();
    }

    return transform;
}


float* SceneIO::parseFloatArray3(const QString& str)
{
    QStringList tokens = str.split(" ", QString::SkipEmptyParts);
    float* array = new float[3];
    for(int i=0; i<tokens.size(); i++)
    {
        array[i] = tokens[i].toFloat();
    }

    return array;
}

